<?php
/**
 * 数据库驱动抽象类
 *
 * @package if
 * @subpackage module.db.drivers
 * @since 1.0.1
 */
abstract class IDbDriver extends IObject {
	protected $config = array();
	
	private $name;
	
	protected $connected = false;
	protected $conn;
	
	//数据类型
	protected $dataTypes = array(
		"VARCHAR" => IDb::TYPE_STR,
		"VARCHAR2" => IDb::TYPE_STR,
		"CHAR" => IDb::TYPE_STR,
		"C" => IDb::TYPE_STR,
		"STRING" => IDb::TYPE_STR,
		"NCHAR" => IDb::TYPE_STR,
		"NVARCHAR" => IDb::TYPE_STR,
		"VARYING" => IDb::TYPE_STR,
		"BPCHAR" => IDb::TYPE_STR,
		"CHARACTER" => IDb::TYPE_STR,
		"INTERVAL" => IDb::TYPE_STR,  # Postgres
		"MACADDR" => IDb::TYPE_STR, # postgres
		"VAR_STRING" => IDb::TYPE_STR, # mysql
		##
		"LONGCHAR" => IDb::TYPE_LOB,
		"TEXT" => IDb::TYPE_LOB,
		"NTEXT" => IDb::TYPE_LOB,
		"M" => IDb::TYPE_LOB,
		"X" => IDb::TYPE_LOB,
		"CLOB" => IDb::TYPE_LOB,
		"NCLOB" => IDb::TYPE_LOB,
		"LVARCHAR" => IDb::TYPE_LOB,
		##
		"BLOB" => IDb::TYPE_LOB,
		"IMAGE" => IDb::TYPE_LOB,
		"BINARY" => IDb::TYPE_LOB,
		"VARBINARY" => IDb::TYPE_LOB,
		"LONGBINARY" => IDb::TYPE_LOB,
		"B" => IDb::TYPE_LOB,
		##
		"YEAR" => IDb::TYPE_DATE, // mysql
		"DATE" => IDb::TYPE_DATE,
		"D" => IDb::TYPE_DATE,
		##
		"UNIQUEIDENTIFIER" => IDb::TYPE_STR, # MS SQL Server
		##
		"SMALLDATETIME" => IDb::TYPE_TIMESTAMP,
		"TIME" => IDb::TYPE_TIMESTAMP,
		"TIMESTAMP" => IDb::TYPE_TIMESTAMP,
		"DATETIME" => IDb::TYPE_TIMESTAMP,
		"TIMESTAMPTZ" => IDb::TYPE_TIMESTAMP,
		"T" => IDb::TYPE_TIMESTAMP,
		"TIMESTAMP WITHOUT TIME ZONE" => IDb::TYPE_TIMESTAMP, // postgresql
		##
		"BOOL" => IDb::TYPE_BOOL,
		"BOOLEAN" => IDb::TYPE_BOOL, 
		"BIT" => IDb::TYPE_BOOL,
		"L" => IDb::TYPE_BOOL,
		##
		"INT" => IDb::TYPE_INT,
		"INT2" => IDb::TYPE_INT,
		"INT4" => IDb::TYPE_INT,
		"INT8" => IDb::TYPE_INT,
		"INTEGER" => IDb::TYPE_INT,
		"INTEGER UNSIGNED" => IDb::TYPE_INT,
		"SHORT" => IDb::TYPE_INT,
		"TINYINT" => IDb::TYPE_INT,
		"SMALLINT" => IDb::TYPE_INT,
		"I" => IDb::TYPE_INT,
		##
		"LONG" => IDb::TYPE_LONG, // interbase is numeric, oci8 is blob
		"BIGINT" => IDb::TYPE_LONG, // this is bigger than PHP 32-bit integers
		"DECIMAL" => IDb::TYPE_FLOAT,
		"DEC" => IDb::TYPE_FLOAT,
		"REAL" => IDb::TYPE_FLOAT,
		"DOUBLE" => IDb::TYPE_FLOAT,
		"DOUBLE PRECISION" => IDb::TYPE_FLOAT,
		"SMALLFLOAT" => IDb::TYPE_FLOAT,
		"FLOAT" => IDb::TYPE_FLOAT,
		"NUMBER" => IDb::TYPE_FLOAT,
		"NUM" => IDb::TYPE_FLOAT,
		"NUMERIC" => IDb::TYPE_FLOAT,
		"MONEY" => IDb::TYPE_FLOAT,
		
		## informix 9.2
		"SQLINT" => IDb::TYPE_INT, 
		"SQLSERIAL" => IDb::TYPE_INT, 
		"SQLSMINT" => IDb::TYPE_INT, 
		"SQLSMFLOAT" => IDb::TYPE_FLOAT, 
		"SQLFLOAT" => IDb::TYPE_FLOAT, 
		"SQLMONEY" => IDb::TYPE_FLOAT, 
		"SQLDECIMAL" => IDb::TYPE_FLOAT, 
		"SQLDATE" => IDb::TYPE_DATE, 
		"SQLVCHAR" => IDb::TYPE_STR, 
		"SQLCHAR" => IDb::TYPE_STR, 
		"SQLDTIME" => IDb::TYPE_TIMESTAMP, 
		"SQLINTERVAL" => IDb::TYPE_FLOAT, 
		"SQLBYTES" => IDb::TYPE_LOB, 
		"SQLTEXT" => IDb::TYPE_LOB,
		 ## informix 10
		"SQLINT8" => IDb::TYPE_INT,
		"SQLSERIAL8" => IDb::TYPE_INT,
		"SQLNCHAR" => IDb::TYPE_STR,
		"SQLNVCHAR" => IDb::TYPE_STR,
		"SQLLVARCHAR" => IDb::TYPE_LOB,
		"SQLBOOL" => IDb::TYPE_BOOL
		);
	protected $defaultDataType = IDb::TYPE_STR;
	
	//引住关键字
	protected $startQuote;
	protected $endQuote;
	
	//基本命令
	protected $commands = array(
		"begin"	   => "BEGIN",
		"commit"   => "COMMIT",
		"rollback" => "ROLLBACK"
	);
	
	protected $metaTableSQL;
	
	protected $support = array(
		"offset" => true
	);
	
	protected $default = array();
	
	/**
	 * 构造器
	 *
	 * @param array $config 连接配置
	 * @since 1.0
	 */
	function __construct(array $config) {
		$this->config = $config;
		$this->checkOptions();
	}
	
	protected function configs() {
		return $this->config;
	}
	
	protected function config($option) {
		if (array_key_exists($option, $this->config)) {
			return $this->config[$option];
		}
		if(array_key_exists($option, $this->default)) {
			return $this->default[$option];
		}
		return null;
	}
	
	protected function checkOptions() {
		$required = array("host", "dbname", "username", "password");
		foreach ($required as $option) {
			if (!array_key_exists($option, $this->config)) {
				throw new IDbException("can not find '{$option}' in db config", 0x100070);
			}
		}
	}
	
	/**
	 * 预处理一个SQL
	 *
	 * @param string $sql 原始SQL
	 * @param array $params 参数
	 * @return IDbStatement
	 * @since 1.0
	 */
	function prepare($sql, $params = array()) {
		return new IDbStatement($this, $sql, $params);
	}
	
	/**
	 * 取得驱动的名字
	 *
	 * @return string
	 * @since 1.0
	 */	
	function name() {
		return $this->name;
	}
	
	/**
	 * 设置驱动的名字
	 *
	 * @param string $name 驱动的名字
	 */	
	function setName($name) {
		$this->name = $name;
	}
	
	/**
	 * 引住数据
	 *
	 * @param mixed $data 需要加引号的数据
	 * @return mixed
	 * @since 1.0
	 */	
	function quote($data) {
		if (if_is_instance_of($data, "IDbExpression")) {
			return $data->value();
		}
		return $this->nQuote($data);
	}
	
	/**
	 * 连接数据库
	 *
	 * @since 1.0
	 */	
	function connect() {
		if (!$this->connected) {
			$this->conn = $this->nConnect();
			$this->connected = (bool)$this->conn;
		}
		return $this->conn;
	}
	
	/**
	 * 关闭连接
	 *
	 * @since 1.0
	 * @return boolean
	 */	
	function close() {
		$bool = $this->nClose();
		if ($bool) {
			$this->conn = null;
			$this->connected = false;
		}
		return $bool;
	}
	
	protected function dataType($column) {
		$nativeType = null;
		if (is_object($column)) {
			$nativeType = $column->type;
		}
		else if (is_array($column)) {
			$nativeType = $column["type"];
		}
		$nativeType = strtoupper($nativeType);
		return isset($this->dataTypes[$nativeType]) ? $this->dataTypes[$nativeType] : $this->defaultDataType;
	}
	
	/**
	 * 引住关键字
	 *
	 * @param string $keyword 关键字
	 * @return string
	 * @since 1.0
	 */	
	function quoteKeyword($keyword) {
		return $this->startQuote . $keyword . $this->endQuote;
	}
	
	/**
	 * 开始事务
	 *
	 * @since 1.0
	 */	
	function beginTransaction() {
		$this->nativeExec($this->commands["begin"]);
	}
	
	/**
	 * 提交事务
	 *
	 * @since 1.0
	 */	
	function commit() {
		$this->nativeExec($this->commands["commit"]);
	}
	
	/**
	 * 回滚事务
	 *
	 * @since 1.0
	 */	
	function rollback() {
		$this->nativeExec($this->commands["rollback"]);
	}
	
	/**
	 * 执行一条语句，返回预处理语句
	 *
	 * @param string $sql SQL
	 * @param integer $fetchType 返回方式，参见IDb::FETCH_*
	 * @return IDbStatement
	 * @since 1.0
	 */
	function query($sql, $fetchType = IDb::FETCH_ASSOC) {
		$stmt = $this->prepare($sql);
		$args = func_get_args();
		unset($args[0]);
		call_user_func_array(array($stmt, "setFetchMode"), $args);
		$stmt->execute();
		return $stmt;
	}
	
	/**
	 * 取得所有记录
	 *
	 * @param mixed $result nativeExec返回的结果集
	 * @param integer $fetchType 返回方式，参见IDb::FETCH_*
	 * @param integer $columnIndex 列序号，对于$fetchType为IDb::FETCH_COLUMN有效
	 * @since 1.0
	 */	
	function fetchAll($result, $fetchType = IDb::FETCH_BOTH, $columnIndex = 0) {
		$array = array();
		$args = func_get_args();
		while ($row = call_user_func_array(array($this, "fetch"), $args)) {
			$array[] = $row;
		}
		return $array;
	}	
	
	/**
	 * 是否支持某一个行为或参数
	 *
	 * @param string $param 行为或参数
	 * @return boolean
	 * @since 1.0.1
	 */	
	function support($param) {
		return (array_key_exists($param, $this->support) && $this->support[$param]);	
	}
	
	/**
	 * 取得数据表列表
	 *
	 * @return array
	 * @since 1.0
	 */
	function tableList() {
		if (strstr($this->metaTableSQL, "%s")) {
			$this->metaTableSQL = sprintf($this->metaTableSQL, $this->quoteKeyword($this->config("dbname")));
		}
		return $this->query($this->metaTableSQL)->fetchAll(IDb::FETCH_COLUMN, 0);
	}
	
	/**
	 * 给SQL加入limit条件
	 *
	 * @param string $sql SQL
	 * @param integer $count 总数
	 * @param integer $offset 开始位置
	 * @return string
	 * @since 1.0
	 */
	function limit($sql, $count, $offset = 0) {
		$count = intval($count);
		$offset = intval($offset);
		if ($count < 0) {
			throw new IDbException("sql limit count can not be lower than 0", 0x100071);
		}
		if ($offset < 0) {
			throw new IDbException("sql limit offset can not be lower than 0", 0x100072);
		}
		if ($count == 0) {
			return $sql;
		}
		return $this->nLimit($sql, $count, $offset);
	}		
	
	protected abstract function nQuote($data);
	protected abstract function nConnect();
	protected abstract function nClose();
	protected abstract function nLimit($sql, $count, $offset = 0);
	
	/**
	 * 取得表的描述
	 *
	 * @param string $name 表名称
	 * @return array
	 * @since 1.0
	 */
	public abstract function describeTable($name);	
	
	/**
	 * 取得最后插入的ID
	 *
	 * @param string $name 可能需要的字段名称
	 * @return string|integer
	 * @since 1.0
	 */
	public abstract function lastInsertId($name = null);	
	
	/**
	 * 执行一条语句，并返回影响的行数
	 *
	 * @param string $sql SQL
	 * @return integer
	 * @since 1.0
	 */
	public abstract function exec($sql);	
	
	/**
	 * 利用原生的方法/函数执行语句，并返回执行结果
	 *
	 * @param string $sql SQL
	 * @return mixed
	 * @since 1.0
	 */
	public abstract function nativeExec($sql);	
	
	/**
	 * 取得一行记录
	 *
	 * @param mixed $result nativeExec返回的结果集
	 * @param integer $fetchType 返回方式，参见IDb::FETCH_*
	 * @param integer $columnIndex 列序号，对于$fetchType为IDb::FETCH_COLUMN有效
	 * @since 1.0
	 */
	public abstract function fetch($result, $fetchType = IDb::FETCH_BOTH, $columnIndex = 0);	
	
	/**
	 * 取得错误
	 *
	 * 返回格式为：
	 * array (
	 *   "code" => 1002,
	 * 	 "message" => "syntax error"
	 * )
	 * 
	 * 如果返回是一个空数组，意味着没有错误发生
	 * 
	 * @return array
	 * @since 1.0
	 */
	public abstract function error();	
	
	/**
	 * 取得影响的行数
	 *
	 * @param mixed $result nativeExec的结果集
	 * @return integer
	 * @since 1.0
	 */
	public abstract function rowCount($result);	
	
	/**
	 * 移动游标到某个offset
	 *
	 * @param mixed $result nativeExec的结果集
	 * @param integer $offset 游标位置
	 * @return boolean
	 * @since 1.0.1
	 */
	public abstract function move($result, $offset);	
}

?>